package tianrun.ziguan.api.calculate.config.service.cal.asset.impl;

import com.alibaba.excel.EasyExcel;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.additional.query.impl.LambdaQueryChainWrapper;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import org.apache.shiro.util.Assert;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import tianrun.ziguan.api.calculate.config.config.influxdb.InfluxDbName;
import tianrun.ziguan.api.calculate.config.constant.RedisKeyConstants;
import tianrun.ziguan.api.calculate.config.constant.StringPools;
import tianrun.ziguan.api.calculate.config.dto.excel.TimeValueImport;
import tianrun.ziguan.api.calculate.config.dto.request.*;
import tianrun.ziguan.api.calculate.config.dto.request.common.DeleteBody;
import tianrun.ziguan.api.calculate.config.dto.response.AssetNameBody;
import tianrun.ziguan.api.calculate.config.dao.TTemplateMapper;
import tianrun.ziguan.api.calculate.config.dto.response.AssetLabelDataBody;
import tianrun.ziguan.api.calculate.config.dto.response.IotValue;
import tianrun.ziguan.api.calculate.config.entity.TAssetEdgeInputInstance;
import tianrun.ziguan.api.calculate.config.entity.TAssetEdgeInputModule;
import tianrun.ziguan.api.calculate.config.entity.TCalAsset;
import tianrun.ziguan.api.calculate.config.dao.TCalAssetMapper;
import tianrun.ziguan.api.calculate.config.entity.TTemplate;
import tianrun.ziguan.api.calculate.config.service.asset.instance.ITAssetEdgeInputInstanceService;
import tianrun.ziguan.api.calculate.config.service.asset.module.ITAssetEdgeInputModuleService;
import tianrun.ziguan.api.calculate.config.service.cal.asset.ITCalAssetService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import tianrun.ziguan.api.calculate.config.service.common.CommonService;
import tianrun.ziguan.api.calculate.config.service.influxdb.IInfluxDbService;
import tianrun.ziguan.api.calculate.config.service.redis.IRedisService;
import tianrun.ziguan.api.calculate.config.service.sync.SyncService;
import tianrun.ziguan.api.calculate.config.utils.CollectionUtil;
import tianrun.ziguan.api.calculate.config.utils.StringUtil;
import tianrun.ziguan.api.common.util.ListUtil;
import tianrun.ziguan.api.common.util.SpringContextUtil;

import java.io.IOException;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author zhaoqi
 */
@Service
public class TCalAssetServiceImpl extends ServiceImpl<TCalAssetMapper, TCalAsset> implements ITCalAssetService {

    private final IRedisService redisService;
    private final ITAssetEdgeInputInstanceService assetEdgeInputInstanceService;
    private final TTemplateMapper templateMapper;
    private final ITAssetEdgeInputModuleService assetEdgeInputModuleService;
    @Value("${spring.redis.databaseByData}")
    private Integer databaseByData;
    private final IInfluxDbService influxDbService;


    public TCalAssetServiceImpl(IRedisService redisService, ITAssetEdgeInputInstanceService assetEdgeInputInstanceService, TTemplateMapper templateMapper, ITAssetEdgeInputModuleService assetEdgeInputModuleService, IInfluxDbService influxDbService) {
        this.redisService = redisService;
        this.assetEdgeInputInstanceService = assetEdgeInputInstanceService;
        this.templateMapper = templateMapper;
        this.assetEdgeInputModuleService = assetEdgeInputModuleService;
        this.influxDbService = influxDbService;
    }

    @Override
    public List<TCalAsset> queryAssetList(String assetGroupName) {
        return baseMapper.queryAssetList(assetGroupName);
    }

    @Override
    public List<TCalAsset> queryAssetListByTemplateIds(Set<Long> ids) {
        if (ListUtil.isEmpty(ids)) return Lists.newArrayList();
        return list(Wrappers.<TCalAsset>lambdaQuery().in(TCalAsset::getTemplateId, ids));
    }

    @Override
    public List<TCalAsset> queryChildAssets(String assetNum) {
        if (StringUtil.isEmpty(assetNum)) return Lists.newArrayList();
        return list(Wrappers.<TCalAsset>lambdaQuery().in(TCalAsset::getParentAssetNum, assetNum));
    }

    @Override
    public List<TCalAsset> getByIds(Set<Long> ids) {
        if (CollectionUtil.isEmpty(ids)) return Lists.newArrayList();
        return list(Wrappers.<TCalAsset>lambdaQuery().in(TCalAsset::getId, ids));
    }

    @Override
    public IPage<AssetLabelDataBody> getLabelData(AssetDataParam param) {
        Long assetId = param.getAssetId();
        String assetNum = param.getAssetNum();
        List<TAssetEdgeInputModule> modules = assetEdgeInputModuleService.list(Wrappers.<TAssetEdgeInputModule>lambdaQuery().eq(TAssetEdgeInputModule::getCalAssetId, assetId));
        if (modules.isEmpty()) return new Page<>();
        LambdaQueryWrapper<TAssetEdgeInputInstance> queryWrapper = Wrappers.<TAssetEdgeInputInstance>lambdaQuery().
                in(TAssetEdgeInputInstance::getModuleId, modules.stream().map(TAssetEdgeInputModule::getId).collect(Collectors.toSet())).
                like(StringUtil.isNotEmpty(param.getLabel()),TAssetEdgeInputInstance::getLabel,param.getLabel());
        IPage<TAssetEdgeInputInstance> page = assetEdgeInputInstanceService.page(new Page<>(param.getCurrentPage(), param.getSize()), queryWrapper);
        Set<String> labels = page.getRecords().stream().map(TAssetEdgeInputInstance::getLabel).collect(Collectors.toSet());
        List<IotValue> iotValues = getCacheData(assetNum, labels);
        IotValue empty = new IotValue();

        return page.convert(instance -> {
            IotValue value = iotValues.stream().filter(iotValue -> Objects.equals(iotValue.getLabel(), instance.getLabel())).findFirst().orElse(empty);
            return AssetLabelDataBody.of(instance.getLabel(), value.getTime(), instance.getName(), value.getValue());
        });
    }

    private List<IotValue> getCacheData(String assetNum, Set<String> labels) {
        List<Object> objects = redisService.hGet(databaseByData, String.format(RedisKeyConstants.ASSET_REAL_DATA_PREFIX, assetNum), labels);
        return objects.stream().filter(Objects::nonNull).map(o -> JSONObject.parseObject(o.toString(), IotValue.class)).collect(Collectors.toList());
    }

    /**
     * 重名校验
     *
     * @param entityList
     * @return
     */
    @Override
    @Transactional
    public void saveOrUpdateBatchByGroup(Collection<TCalAsset> entityList) {
        if (entityList.isEmpty()) return;

        Map<String, Integer> countMap = new HashMap<>();
        entityList.forEach(asset -> countMap.put(asset.getAssetNum(), countMap.getOrDefault(asset.getAssetNum(), 0) + 1));
        // 找出出现次数大于1的字符串
        List<String> duplicates = countMap.entrySet().stream()
                .filter(entry -> entry.getValue() > 1)
                .map(Map.Entry::getKey)
                .collect(Collectors.toList());
        Assert.isTrue(duplicates.isEmpty(), "资产编号重复:" + duplicates);

        List<Long> haveIds = entityList.stream().map(TCalAsset::getId).filter(Objects::nonNull).collect(Collectors.toList());
        Collection<TCalAsset> oldAssets = haveIds.isEmpty() ? Lists.newArrayList() : listByIds(haveIds);
        Map<String, TCalAsset> oldAssetMap = ListUtil.toMap(oldAssets, TCalAsset::getAssetNum, Function.identity());
        //不使用自带的事务
        entityList.forEach(asset -> {
            boolean b = Objects.isNull(asset.getId()) ? save(asset) : updateById(asset);
        });
        Set<Long> ids = entityList.stream().map(TCalAsset::getId).filter(Objects::nonNull).collect(Collectors.toSet());
        List<TCalAsset> assets = getByIds(ids);
        Set<String> assetNums = assets.stream().map(TCalAsset::getAssetNum).collect(Collectors.toSet());
        List<TCalAsset> list = list(Wrappers.<TCalAsset>lambdaQuery().in(TCalAsset::getAssetNum, assetNums).notIn(TCalAsset::getId, ids));
        Assert.isTrue(list.isEmpty(), list.stream().map(TCalAsset::getAssetNum).collect(Collectors.toList()) + "资产编号已重复,无法保存,请检查");

        //维护父子级关系
        maintainParentNum(oldAssetMap, assets);
        return;
    }

    private void maintainParentNum(Map<String, TCalAsset> oldAssetMap, List<TCalAsset> newAssets) {
        Map<Long, TCalAsset> newAssetMap = ListUtil.toMap(newAssets, TCalAsset::getId, Function.identity());
        Map<Long, List<TCalAsset>> groupIdParentAssetNumMap = ListUtil.groupList(newAssets, TCalAsset::getAssetGroupId, Function.identity());
        ArrayList<TCalAsset> updates = Lists.newArrayList();
        StringBuilder sb = new StringBuilder();
        groupIdParentAssetNumMap.forEach((groupId, values) -> {
            Set<String> parentAssetNums = values.stream().map(TCalAsset::getParentAssetNum).collect(Collectors.toSet());
            parentAssetNums.removeIf(StringUtil::isEmpty);
            if (parentAssetNums.isEmpty()) return;
            LambdaQueryChainWrapper<TCalAsset> wrapper = lambdaQuery().in(TCalAsset::getAssetGroupId, groupId).in(TCalAsset::getAssetNum, parentAssetNums);
            if (parentAssetNums.size() == wrapper.count()) return;
            List<TCalAsset> list1 = wrapper.list();
            List<String> collect = list1.stream().map(TCalAsset::getAssetNum).collect(Collectors.toList());
            values.forEach(asset -> {
                if (asset.getParentAssetNum().isEmpty() || collect.contains(asset.getParentAssetNum())) return;
                //尝试自动维护
                TCalAsset oldAsset = oldAssetMap.get(asset.getParentAssetNum());
                if (oldAsset == null) {
                    sb.append(asset.getAssetNum()).append(" 的父级资产节点 ").append(asset.getParentAssetNum()).append("无法找到！").append(StringPools.NEWLINE);
                    return;
                }
                Long id = oldAsset.getId();
                TCalAsset newAsset = newAssetMap.get(id);
                if (newAsset == null) {
                    sb.append(asset.getAssetNum()).append(" 的父级资产节点 ").append(asset.getParentAssetNum()).append("无法找到！").append(StringPools.NEWLINE);
                    return;
                }
                asset.setParentAssetNum(newAsset.getAssetNum());
                updates.add(asset);
            });
        });
        Assert.isTrue(sb.toString().isEmpty(), sb.toString().trim());

        if (!updates.isEmpty()) {
            updateBatchById(updates);
        }
    }

    @Override
    public List<AssetNameBody> getAssetNameList(AssetNameParam param) {
        Long groupId = param.getGroupId();
        List<TCalAsset> list = lambdaQuery().eq(TCalAsset::getAssetGroupId, groupId).list();
        if (list.isEmpty()) return Lists.newArrayList();
        Set<Long> templateIds = list.stream().map(TCalAsset::getTemplateId).collect(Collectors.toSet());
        List<TTemplate> templates = templateMapper.selectList(Wrappers.<TTemplate>lambdaQuery().in(TTemplate::getId, templateIds));
        Map<Long, String> tTemplateMap = ListUtil.toMap(templates, TTemplate::getId, TTemplate::getName);
        tTemplateMap.put(0L, "(虚拟资产)无模板");
        return list.stream().map(asset -> {
            AssetNameBody assetNameBody = new AssetNameBody();
            BeanUtils.copyProperties(asset, assetNameBody);
            assetNameBody.setTemplateName(tTemplateMap.get(asset.getTemplateId()));
            return assetNameBody;
        }).collect(Collectors.toList());
    }

    @Override
    public boolean clearData(ClearDataParam param) {
        return influxDbService.deleteLabelByDate(InfluxDbName.SOURCE_HISTORY, InfluxDbName.SOURCE_HISTORY_DB_NAME, param.getAssetNum(), param.getLabel(), param.getStartDate(), param.getEndDate());
    }

    @Override
    public void addData(AddDataParam param, MultipartFile multipartFile) throws IOException {
        EasyExcel.read(multipartFile.getInputStream(), TimeValueImport.class, new TimeValueListener(influxDbService, param)).sheet().doRead();
    }

    @Transactional
    @Override
    public boolean updateTemplate(UpdateTemplateParam param) {
        Long calAssetId = param.getCalAssetId();
        TCalAsset tCalAsset = getById(calAssetId);
        Long templateId = tCalAsset.getTemplateId();
        Assert.isTrue(!Objects.equals(templateId, param.getTemplateId()), "模板无变化,不进行替换操作");
        TCalAsset update = new TCalAsset();
        update.setTemplateId(param.getTemplateId());
        update.setId(calAssetId);
        boolean b = updateById(update);

        if (!b) return false;
        //先假删资产，借用通用删除删除旧的组件，再同步模板
        CommonService commonService = SpringContextUtil.getBean(CommonService.class);
        DeleteBody body = new DeleteBody();
        body.setTableName("t_cal_asset");
        body.setIds(Sets.newHashSet(calAssetId));
        commonService.delete(body);
        undeleteById(calAssetId);
        SyncService syncService = SpringContextUtil.getBean(SyncService.class);
        syncService.doSync(Collections.singletonList(update));
        return b;
    }

    private boolean undeleteById(Long id){
        return baseMapper.undeleteById(id) != 0;
    }

    @Override
    public TCalAsset getByAssetNum(String assetNum) {
        return lambdaQuery().eq(TCalAsset::getAssetNum,assetNum).last("limit 1").one();
    }
}
